using System;
using System.Collections.Generic;
using System.Reflection;
using NUnit.Framework;
using TypeMock;
using ValidationFramework.Reflection;

namespace ValidationFramework.Tests
{

	public class ParameterValidationManagerTests
	{
		[TestFixture]
        public class TryValidate : BaseTypeMock
		{
			public IParameters parameters;
			Mock mock;
			public static readonly RuntimeMethodHandle handle;
			
			public  readonly Action methodDelegate;
			public TryValidate()
			{
				methodDelegate = MethodForHandle;
			}
			static TryValidate()
			{
				handle = typeof(TryValidate).GetMethod("MethodForHandle").MethodHandle;
			}

			public void MethodForHandle() { }


			[SetUp]
			public override void SetUp()
			{
				base.SetUp();
				mock = MockManager.Mock(typeof(ParameterValidationManager));
				parameters = mock.ExpectAndReturn("InternalValidate", null);
			}

			[TearDown]
			public override void TearDown()
			{
				mock.Verify();
				base.TearDown();
			}


			[Test]
			public void HandleTryValidate1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, false, false);
				ParameterValidationManager.TryValidate("target", handle, "ruleSet", new object[] { "param" });
			}

			[Test]
			public void HandleTryValidate2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", false, false);
				ParameterValidationManager.TryValidate("target", handle, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void HandleTryValidate3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, false, false);
				ParameterValidationManager.TryValidate("target", handle, new object[] { "param" });
			}
			[Test]
			public void DelegateTryValidate1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, false, false);
				ParameterValidationManager.TryValidate("target", methodDelegate, "ruleSet", new object[] { "param" });
			}

			[Test]
			public void DelegateTryValidate2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", false, false);
				ParameterValidationManager.TryValidate("target", methodDelegate, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void DelegateTryValidate3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, false, false);
				ParameterValidationManager.TryValidate("target", methodDelegate, new object[] { "param" });
			}

		}
		[TestFixture]
        public class Validate : BaseTypeMock
		{
			public IParameters parameters;
			Mock mock;
			public static readonly RuntimeMethodHandle handle;
			
			public  readonly Action methodDelegate;
			public Validate()
			{
				methodDelegate = MethodForHandle;
			}
			static Validate()
			{
				handle = typeof(Validate).GetMethod("MethodForHandle").MethodHandle;
			}

			public void MethodForHandle() { }


			[SetUp]
			public override void SetUp()
			{
				base.SetUp();
				mock = MockManager.Mock(typeof(ParameterValidationManager));
				parameters = mock.ExpectAndReturn("InternalValidate", null);
			}

			[TearDown]
			public override void TearDown()
			{
				mock.Verify();
				base.TearDown();
			}

			[Test]
			public void HandleValidate1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, true, false);
				ParameterValidationManager.Validate("target", handle, "ruleSet", new object[] { "param" });
			}
			[Test]
			public void HandleValidate2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", true, false);
				ParameterValidationManager.Validate("target", handle, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void HandleValidate3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, true, false);
				ParameterValidationManager.Validate("target", methodDelegate, new object[] { "param" });
			}

			[Test]
			public void DelegateValidate1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, true, false);
				ParameterValidationManager.Validate("target", methodDelegate, "ruleSet", new object[] { "param" });
			}
			[Test]
			public void DelegateValidate2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", true, false);
				ParameterValidationManager.Validate("target", methodDelegate, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void DelegateValidate3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, true, false);
				ParameterValidationManager.Validate("target", handle, new object[] { "param" });
			}

		}


		[TestFixture]
        public class ThrowException : BaseTypeMock
		{
			public IParameters parameters;
			Mock mock;
			public static readonly RuntimeMethodHandle handle;
			private readonly IList<ValidationError> validationResults = new List<ValidationError>();

			public  readonly Action methodDelegate;
			public ThrowException()
			{
				methodDelegate = MethodForHandle;
			}
			static ThrowException()
			{
				handle = typeof(ThrowException).GetMethod("MethodForHandle").MethodHandle;
			}

			public void MethodForHandle() { }


			[SetUp]
			public override void SetUp()
			{
                base.SetUp();
				mock = MockManager.Mock(typeof(ParameterValidationManager));
				parameters = mock.ExpectAndReturn("InternalValidate", null);
			}

			[TearDown]
			public override void TearDown()
			{
				mock.Verify();
                base.TearDown();
			}



			[Test]
			public void HandleThrowException1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, true, true);
				ParameterValidationManager.ThrowException("target", handle, "ruleSet", new object[] { "param" });
			}

			[Test]
			public void HandleThrowException2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", true, true);
				ParameterValidationManager.ThrowException("target", handle, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void HandleThrowException3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, true, true);
				ParameterValidationManager.ThrowException("target", handle, new object[] { "param" });
			}

			[Test]
			public void DelegateThrowException1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, true, true);
				ParameterValidationManager.ThrowException("target", methodDelegate, "ruleSet", new object[] { "param" });
			}

			[Test]
			public void DelegateThrowException2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", true, true);
				ParameterValidationManager.ThrowException("target", methodDelegate, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void DelegateThrowException3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, true, true);
				ParameterValidationManager.ThrowException("target", methodDelegate, new object[] { "param" });
			}
		}
		[TestFixture]
        public class TryThrowException : BaseTypeMock
		{
			public IParameters parameters;
			Mock mock;
			public static readonly RuntimeMethodHandle handle;
			private readonly IList<ValidationError> validationResults = new List<ValidationError>();
			
			public  readonly Action methodDelegate;
			public TryThrowException()
			{
				methodDelegate = MethodForHandle;
			}

			static TryThrowException()
			{
				handle = typeof(TryThrowException).GetMethod("MethodForHandle").MethodHandle;
			}

			public void MethodForHandle() { }


			[SetUp]
			public override void SetUp()
			{
                base.SetUp();
				mock = MockManager.Mock(typeof(ParameterValidationManager));
				parameters = mock.ExpectAndReturn("InternalValidate", null);
			}

			[TearDown]
			public override void TearDown()
			{
				mock.Verify();
                base.TearDown();
			}



			[Test]
			public void HandleTryThrowException1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, false, true);
				ParameterValidationManager.TryThrowException("target", handle, "ruleSet", new object[] { "param" });
			}
			[Test]
			public void HandleTryThrowException2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", false, true);
				ParameterValidationManager.TryThrowException("target", handle, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void HandleTryThrowException3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, false, true);
				ParameterValidationManager.TryThrowException("target", handle, new object[] { "param" });
			}

			[Test]
			public void DelegateTryThrowException1Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", null, false, true);
				ParameterValidationManager.TryThrowException("target", methodDelegate, "ruleSet", new object[] { "param" });
			}
			[Test]
			public void DelegateTryThrowException2Test()
			{
				parameters.Args(handle, "ruleSet", new object[] { "param" }, "target", "context", false, true);
				ParameterValidationManager.TryThrowException("target", methodDelegate, "ruleSet", "context", new object[] { "param" });
			}

			[Test]
			public void DelegateTryThrowException3Test()
			{
				parameters.Args(handle, null, new object[] { "param" }, "target", null, false, true);
				ParameterValidationManager.TryThrowException("target", methodDelegate, new object[] { "param" });
			}
		}


		public class ParameterValidationManagerTestsBase
		{
			public static readonly RuntimeMethodHandle doSomethingStaticHandle =
				typeof(ParameterValidationManagerTestsBase).GetMethod("DoSomething").MethodHandle;
			public static bool isRuleValid;

			private static readonly CustomRule customRuleWithRuleSet;
			private static readonly CustomRule customRuleWithNoRuleSet;

			public static void CheckIsNotEmpty(object sender, CustomValidationEventArgs e)
			{
				e.IsValid = isRuleValid;
			}

			static ParameterValidationManagerTestsBase()
			{
				customRuleWithRuleSet = new CustomRule(null, false, CheckIsNotEmpty, "foo");
				customRuleWithNoRuleSet = new CustomRule(null, false, CheckIsNotEmpty, "foo");
			}

			public static void SetRuleWithRuleSet()
			{
                GetParamDataRules().Add(customRuleWithRuleSet, "foo");
			}

			public static void SetRuleWithNoRuleSet()
			{
				GetParamDataRules().Add(customRuleWithNoRuleSet, null);
			}


			private static RuleCollection GetParamDataRules()
			{
			    var ruleCollection = MethodCache.GetMethod(doSomethingStaticHandle).Parameters["paramData"].Rules;
			    ruleCollection.Clear();
                return ruleCollection;
			}


		    public static void DoSomething(int paramData)
			{
			}

			[SetUp]
			public void SetUp()
			{
				GetParamDataRules().Clear();
			}

			[TearDown]
			public void TearDown()
			{
				GetParamDataRules().Clear();
			}

		}


		
		[TestFixture]
		public class InternalValidate : ParameterValidationManagerTestsBase
		{

			private static IList<ValidationError> CallInternalValidate(RuntimeMethodHandle runtimeMethodHandle, string ruleSet, object context, bool throwException, bool throwOnFirstInvalid)
			{
				const BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic;
				var internalValidateMethodInfo = typeof(ParameterValidationManager).GetMethod("InternalValidate", bindingFlags);
				var methodBeingValidatedParams = new object[] { 6 };
				var internalThrowExceptionParams = new[] { runtimeMethodHandle, ruleSet, methodBeingValidatedParams, null, context, throwException, throwOnFirstInvalid };

				try
				{
					return (IList<ValidationError>)internalValidateMethodInfo.Invoke(null, bindingFlags, null, internalThrowExceptionParams, null);
				}
				catch (TargetInvocationException e)
				{
					throw e.InnerException;
				}

			}
			[Test]
			[ExpectedException(typeof(ArgumentException), ExpectedMessage = "Incorrect number of parameters. All parameter have to be provided in the same order that they exist in the method. Method name=DoSomething. Expected parameter count=1. Parameters passed in count=2.\r\nParameter name: parameters")]
			public void ExceptionIncorrectParamCount()
			{
				const BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic;
				var internalValidateMethodInfo = typeof(ParameterValidationManager).GetMethod("InternalValidate", bindingFlags);
				var methodBeingValidatedParams = new object[] { 6, 6 };
				var internalThrowExceptionParams = new object[] { doSomethingStaticHandle, null, methodBeingValidatedParams, null, null, false, false};

				try
				{
					internalValidateMethodInfo.Invoke(null, bindingFlags, null, internalThrowExceptionParams, null);
				}
				catch (TargetInvocationException e)
				{
					throw e.InnerException;
				}

			}


			// ruleSet = string.empty
			[Test]
			[ExpectedException(typeof(ArgumentException), ExpectedMessage = "String cannot be empty.\r\nParameter name: ruleSet")]
			public void RuleSetEmpty()
			{
				SetRuleWithNoRuleSet();
				isRuleValid = false;
				CallInternalValidate(doSomethingStaticHandle, string.Empty, null, false, false);
			}


			// ruleSet = null, invalid parameter
			[Test]
			public void RuleSetNullInvalidParameter()
			{
				SetRuleWithNoRuleSet();
				isRuleValid = false;
				var validate = CallInternalValidate(doSomethingStaticHandle, null, null, false, false);
				Assert.AreEqual(1, validate.Count);
			}


			// ruleSet = null, valid parameter
			[Test]
			public void RuleSetNullValidParameter()
			{
				SetRuleWithNoRuleSet();
				isRuleValid = true;
				var validate = CallInternalValidate(doSomethingStaticHandle, null, null, false, false);
				Assert.AreEqual(0, validate.Count);
			}


			// ruleSet != null, invalid parameter, valid ruleSet
			[Test]
			public void RuleSetNotNullInvalidParameterValidRuleSet()
			{
				SetRuleWithRuleSet();
				isRuleValid = false;
				var validate = CallInternalValidate(doSomethingStaticHandle, "foo", null, false, false);
				Assert.AreEqual(1, validate.Count);
			}

			// ruleSet != null, invalid parameter, valid ruleSet
			[Test]
			[ExpectedException(typeof(ArgumentException))]
			public void ExceptionthrowOnFirstInvalid()
			{
				SetRuleWithRuleSet();
				isRuleValid = false;
				var validate = CallInternalValidate(doSomethingStaticHandle, "foo", null, false, true);
				Assert.AreEqual(1, validate.Count);
			}


			// ruleSet != null, valid parameter, invalid ruleSet
			[Test]
			public void RuleSetNotNullValidParameterInvalidRuleSet()
			{
				SetRuleWithRuleSet();
				isRuleValid = true;
				CallInternalValidate(doSomethingStaticHandle, "foo2", null, false, false);
			}


			// ruleSet != null, valid parameter, valid ruleSet
			[Test]
			public void RuleSetNotNullValidParameterValidRuleSet()
			{
				SetRuleWithRuleSet();
				isRuleValid = true;
				CallInternalValidate(doSomethingStaticHandle, "foo", null, false, false);
			}


			// ruleSet != null, invalid parameter, invalid ruleSet
			[Test]
			public void RuleSetNotNullInValidParameterInvalidRuleSet()
			{
				SetRuleWithRuleSet();
				isRuleValid = false;
				CallInternalValidate(doSomethingStaticHandle, "foo2", null, false, false);
			}


			// throwException = true, rule not found, ruleSet = null
			[Test]
			[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "No members could be found containing rules.")]
			public void ThrowExceptionRuleNotFoundRuleSetNull()
			{
				isRuleValid = false;
				CallInternalValidate(doSomethingStaticHandle, null, null, true, false);
			}


			// throwException = true, rule not found, ruleSet != null
			[Test]
			[ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "No members could be found containing rules with the ruleSet 'FOO2'.")]
			public void ThrowExceptionRuleNotFoundRuleSetNotNull()
			{
				isRuleValid = false;
				CallInternalValidate(doSomethingStaticHandle, "foo2", null, true, false);
			}
		}

	}
}